import pyautogui
import pyperclip
import os
import time
import logging
import sys
import inspect
import json

def setup_logging():
    """
    配置日志记录并设置控制台输出编码为 UTF-8，
    确保日志输出的中文不会出现乱码问题。
    """
    # 设置控制台输出编码为 UTF-8
    sys.stdout.reconfigure(encoding='utf-8')
    # 配置日志记录
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # 创建一个 StreamHandler 并设置编码
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)
    # 设置编码为 UTF-8
    console_handler.stream.reconfigure(encoding='utf-8')
    root_logger.addHandler(console_handler)

# setup_logging()

def get_full_image_path(caller_file, relative_path):
    """
    根据传入的调用文件路径和相对路径，生成图片的完整路径。

    :param caller_file: 调用该函数的文件路径
    :param relative_path: 图片相对于调用文件所在目录的相对路径
    :return: 图片的完整路径
    """
    caller_dir = os.path.dirname(os.path.abspath(caller_file))
    return os.path.join(caller_dir, relative_path)

def check_image_exists(caller_file, image_relative_path):
    """
    检查指定相对路径的图片是否存在。

    :param caller_file: 调用该函数的文件路径
    :param image_relative_path: 图片相对于调用文件所在目录的相对路径
    :return: 如果图片存在返回 True，否则返回 False
    """
    image_path = get_full_image_path(caller_file, image_relative_path)
    return os.path.exists(image_path)

def find_and_click(image_relative_path, confidence=0.8, max_retries=3, retry_delay=1):
    """
    在屏幕上查找指定图片并点击其中心位置。

    :param image_relative_path: 要查找的图片相对于调用文件所在目录的相对路径
    :param confidence: 图片匹配的置信度，范围从 0 到 1，默认值为 0.8
    :param max_retries: 最大重试次数，默认值为 3
    :param retry_delay: 每次重试之间的延迟时间（秒），默认值为 1
    :return: 如果找到并点击了图片，则返回 True；否则返回 False
    """
    # 获取调用者的文件路径
    caller_file = inspect.stack()[1].filename
    if not check_image_exists(caller_file, image_relative_path):
        logging.error(f"错误：图片文件不存在于 {get_full_image_path(caller_file, image_relative_path)}")
        return False
    image_path = get_full_image_path(caller_file, image_relative_path)
    retries = 0
    while retries < max_retries:
        try:
            location = pyautogui.locateOnScreen(image_path, confidence=confidence)
            if location:
                x, y = pyautogui.center(location)
                pyautogui.click(x, y)
                logging.info(f"成功找到并点击图片: {image_path}")
                return True
            else:
                logging.info(f"第 {retries + 1} 次尝试未找到图片: {image_path}")
        except FileNotFoundError:
            logging.error(f"图片文件未找到: {image_path}")
            break
        except Exception as e:
            logging.error(f"触发冗余错误: {e}")
            break
        retries += 1
        time.sleep(retry_delay)
    logging.warning(f"达到最大重试次数，未找到图片: {image_path}")
    return False

def find_and_click_point_v2(image_relative_path, point, scale=1.0, padding=0, confidence=0.8, max_retries=3, retry_delay=1):
    """
    在屏幕上查找指定图片并点击图片上指定的点（1 - 9），考虑图片的缩放比例和内距离 padding。
    函数会尝试 0.5 到 1.5 之间的所有缩放比例，直到找到图片并点击指定点或者达到最大重试次数。

    :param image_relative_path: 要查找的图片相对于调用文件所在目录的相对路径
    :param point: 要点击的图片上的点，取值范围为 1 - 9
    :param scale: 图片的初始缩放比例，默认值为 1.0
    :param padding: 内距离，默认值为 0
    :param confidence: 图片匹配的置信度，范围从 0 到 1，默认值为 0.8
    :param max_retries: 最大重试次数，默认值为 3
    :param retry_delay: 每次重试之间的延迟时间（秒），默认值为 1
    :return: 如果找到并点击了图片上指定的点，则返回 True；否则返回 False
    """
    if point not in range(1, 10):
        logging.error(f"无效的点值: {point}，请输入 1 到 9 之间的整数。")
        return False
    # 获取调用者的文件路径
    caller_file = inspect.stack()[1].filename
    if not check_image_exists(caller_file, image_relative_path):
        logging.error(f"错误：图片文件不存在于 {get_full_image_path(caller_file, image_relative_path)}")
        return False
    image_path = get_full_image_path(caller_file, image_relative_path)
    # 尝试 0.5 到 1.5 之间的缩放比例，步长为 0.1
    scales_to_try = [i / 10 for i in range(5, 16)]  
    for current_scale in scales_to_try:
        retries = 0
        while retries < max_retries:
            try:
                import cv2
                import numpy as np
                img = cv2.imread(image_path)
                width = int(img.shape[1] * current_scale)
                height = int(img.shape[0] * current_scale)
                dim = (width, height)
                resized_img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
                temp_path = "temp_resized_image.png"
                cv2.imwrite(temp_path, resized_img)
                location = pyautogui.locateOnScreen(temp_path, confidence=confidence)
                if location:
                    left, top, width, height = location
                    # 计算每个点的坐标，考虑 padding
                    points = [
                        (left + padding, top + padding),  # 1: 左上顶点
                        (left + width // 2, top + padding),  # 2: 中上顶点
                        (left + width - padding, top + padding),  # 3: 右上顶点
                        (left + padding, top + height // 2),  # 4: 中间左边顶点
                        (left + width // 2, top + height // 2),  # 5: 图片上下左右居中点
                        (left + width - padding, top + height // 2),  # 6: 中间右边顶点
                        (left + padding, top + height - padding),  # 7: 左下顶点
                        (left + width // 2, top + height - padding),  # 8: 中下顶点
                        (left + width - padding, top + height - padding)  # 9: 右下顶点
                    ]
                    x, y = points[point - 1]
                    pyautogui.click(x, y)
                    logging.info(f"成功找到并点击图片上的点 {point}，考虑内距离 {padding} 和缩放比例 {current_scale}: {image_path}")
                    os.remove(temp_path)
                    return True
                else:
                    logging.info(f"第 {retries + 1} 次尝试未找到图片，缩放比例 {current_scale}: {image_path}")
            except FileNotFoundError:
                logging.error(f"图片文件未找到: {image_path}")
                break
            except Exception as e:
                logging.error(f"触发冗余错误: {e}")
                break
            retries += 1
            time.sleep(retry_delay)
        # 如果已经找到并点击，跳出缩放比例循环
        if retries < max_retries:
            break
    logging.warning(f"达到最大重试次数，未找到图片: {image_path}")
    return False

def find_and_click_point(image_relative_path, point, padding=0, confidence=0.8, max_retries=3, retry_delay=1):
    """
    在屏幕上查找指定图片并点击图片上指定的点（1 - 9），考虑内距离 padding。

    :param image_relative_path: 要查找的图片相对于调用文件所在目录的相对路径
    :param point: 要点击的图片上的点，取值范围为 1 - 9
    :param padding: 内距离，默认值为 0
    :param confidence: 图片匹配的置信度，范围从 0 到 1，默认值为 0.8
    :param max_retries: 最大重试次数，默认值为 3
    :param retry_delay: 每次重试之间的延迟时间（秒），默认值为 1
    :return: 如果找到并点击了图片上指定的点，则返回 True；否则返回 False
    """
    if point not in range(1, 10):
        logging.error(f"无效的点值: {point}，请输入 1 到 9 之间的整数。")
        return False
    # 获取调用者的文件路径
    caller_file = inspect.stack()[1].filename
    if not check_image_exists(caller_file, image_relative_path):
        logging.error(f"错误：图片文件不存在于 {get_full_image_path(caller_file, image_relative_path)}")
        return False
    image_path = get_full_image_path(caller_file, image_relative_path)
    retries = 0
    while retries < max_retries:
        try:
            location = pyautogui.locateOnScreen(image_path, confidence=confidence)
            if location:
                left, top, width, height = location
                # 计算每个点的坐标，考虑 padding
                points = [
                    (left + padding, top + padding),  # 1: 左上顶点
                    (left + width // 2, top + padding),  # 2: 中上顶点
                    (left + width - padding, top + padding),  # 3: 右上顶点
                    (left + padding, top + height // 2),  # 4: 中间左边顶点
                    (left + width // 2, top + height // 2),  # 5: 图片上下左右居中点
                    (left + width - padding, top + height // 2),  # 6: 中间右边顶点
                    (left + padding, top + height - padding),  # 7: 左下顶点
                    (left + width // 2, top + height - padding),  # 8: 中下顶点
                    (left + width - padding, top + height - padding)  # 9: 右下顶点
                ]
                x, y = points[point - 1]
                pyautogui.click(x, y)
                logging.info(f"成功找到并点击图片上的点 {point}，考虑内距离 {padding}: {image_path}")
                return True
            else:
                logging.info(f"第 {retries + 1} 次尝试未找到图片: {image_path}")
        except FileNotFoundError:
            logging.error(f"图片文件未找到: {image_path}")
            break
        except Exception as e:
            logging.error(f"触发冗余错误: {e}")
            break
        retries += 1
        time.sleep(retry_delay)
    logging.warning(f"达到最大重试次数，未找到图片: {image_path}")
    return False

def find_and_click_offset(image_relative_path, offset_x, offset_y, confidence=0.8, max_retries=3, retry_delay=1):
    """
    在屏幕上查找指定图片并点击图片中心点相对偏移的位置。

    :param image_relative_path: 要查找的图片相对于调用文件所在目录的相对路径
    :param offset_x: 图片中心点在 x 轴上的相对偏移量，正数向右，负数向左
    :param offset_y: 图片中心点在 y 轴上的相对偏移量，正数向上，负数向下
    :param confidence: 图片匹配的置信度，范围从 0 到 1，默认值为 0.8
    :param max_retries: 最大重试次数，默认值为 3
    :param retry_delay: 每次重试之间的延迟时间（秒），默认值为 1
    :return: 如果找到并点击了图片上指定偏移位置，则返回 True；否则返回 False
    """
    # 获取调用者的文件路径
    caller_file = inspect.stack()[1].filename
    if not check_image_exists(caller_file, image_relative_path):
        logging.error(f"错误：图片文件不存在于 {get_full_image_path(caller_file, image_relative_path)}")
        return False
    image_path = get_full_image_path(caller_file, image_relative_path)
    retries = 0
    while retries < max_retries:
        try:
            location = pyautogui.locateOnScreen(image_path, confidence=confidence)
            if location:
                center_x, center_y = pyautogui.center(location)
                click_x = center_x + offset_x
                click_y = center_y + offset_y
                pyautogui.click(click_x, click_y)
                logging.info(f"成功找到并点击图片 {image_path} 中心点偏移位置 (x={offset_x}, y={offset_y})")
                return True
            else:
                logging.info(f"第 {retries + 1} 次尝试未找到图片: {image_path}")
        except FileNotFoundError:
            logging.error(f"图片文件未找到: {image_path}")
            break
        except Exception as e:
            logging.error(f"触发冗余错误: {e}")
            break
        retries += 1
        time.sleep(retry_delay)
    logging.warning(f"达到最大重试次数，未找到图片: {image_path}")
    return False

def mouse_drag_from_center(image_relative_path, offset_x, offset_y, duration=0.5, confidence=0.8, max_retries=3, retry_delay=1):
    """
    在屏幕上查找指定图片，在图片中心点按下鼠标左键，滑动到相对偏移的位置。

    :param image_relative_path: 要查找的图片相对于调用文件所在目录的相对路径
    :param offset_x: 图片中心点在 x 轴上的相对偏移量，正数向右，负数向左
    :param offset_y: 图片中心点在 y 轴上的相对偏移量，正数向上，负数向下
    :param duration: 鼠标拖动的持续时间（秒），默认值为 0.5
    :param confidence: 图片匹配的置信度，范围从 0 到 1，默认值为 0.8
    :param max_retries: 最大重试次数，默认值为 3
    :param retry_delay: 每次重试之间的延迟时间（秒），默认值为 1
    :return: 如果成功完成拖动操作，则返回 True；否则返回 False
    """
    # 获取调用者的文件路径
    caller_file = inspect.stack()[1].filename
    if not check_image_exists(caller_file, image_relative_path):
        logging.error(f"错误：图片文件不存在于 {get_full_image_path(caller_file, image_relative_path)}")
        return False
    image_path = get_full_image_path(caller_file, image_relative_path)
    retries = 0
    while retries < max_retries:
        try:
            location = pyautogui.locateOnScreen(image_path, confidence=confidence)
            if location:
                center_x, center_y = pyautogui.center(location)
                end_x = center_x + offset_x
                end_y = center_y + offset_y
                pyautogui.moveTo(center_x, center_y)
                pyautogui.mouseDown()
                pyautogui.moveTo(end_x, end_y, duration=duration)
                pyautogui.mouseUp()
                logging.info(f"成功在图片 {image_path} 上从中心点拖动到偏移位置 (x={offset_x}, y={offset_y})，用时 {duration} 秒")
                return True
            else:
                logging.info(f"第 {retries + 1} 次尝试未找到图片: {image_path}")
        except FileNotFoundError:
            logging.error(f"图片文件未找到: {image_path}")
            break
        except Exception as e:
            logging.error(f"触发冗余错误: {e}")
            break
        retries += 1
        time.sleep(retry_delay)
    logging.warning(f"达到最大重试次数，未找到图片: {image_path}")
    return False

def mouse_drag_between_points(image_relative_path, start_point, end_point, padding=0, duration=0.5, confidence=0.8, max_retries=3, retry_delay=1):
    """
    在屏幕上查找指定图片，并在图片上指定的起始点按下鼠标左键，拖动到结束点后抬起鼠标左键。

    :param image_relative_path: 要查找的图片相对于调用文件所在目录的相对路径
    :param start_point: 拖动操作的起始点，取值范围为 1 - 9
    :param end_point: 拖动操作的结束点，取值范围为 1 - 9
    :param padding: 内距离，默认值为 0
    :param duration: 鼠标拖动的持续时间（秒），默认值为 0.5
    :param confidence: 图片匹配的置信度，范围从 0 到 1，默认值为 0.8
    :param max_retries: 最大重试次数，默认值为 3
    :param retry_delay: 每次重试之间的延迟时间（秒），默认值为 1
    :return: 如果成功完成拖动操作，则返回 True；否则返回 False
    """
    if start_point not in range(1, 10) or end_point not in range(1, 10):
        logging.error(f"无效的点值: 起始点 {start_point} 或结束点 {end_point}，请输入 1 到 9 之间的整数。")
        return False
    # 获取调用者的文件路径
    caller_file = inspect.stack()[1].filename
    if not check_image_exists(caller_file, image_relative_path):
        logging.error(f"错误：图片文件不存在于 {get_full_image_path(caller_file, image_relative_path)}")
        return False
    image_path = get_full_image_path(caller_file, image_relative_path)
    retries = 0
    while retries < max_retries:
        try:
            location = pyautogui.locateOnScreen(image_path, confidence=confidence)
            if location:
                left, top, width, height = location
                # 计算每个点的坐标，考虑 padding
                points = [
                    (left + padding, top + padding),  # 1: 左上顶点
                    (left + width // 2, top + padding),  # 2: 中上顶点
                    (left + width - padding, top + padding),  # 3: 右上顶点
                    (left + padding, top + height // 2),  # 4: 中间左边顶点
                    (left + width // 2, top + height // 2),  # 5: 图片上下左右居中点
                    (left + width - padding, top + height // 2),  # 6: 中间右边顶点
                    (left + padding, top + height - padding),  # 7: 左下顶点
                    (left + width // 2, top + height - padding),  # 8: 中下顶点
                    (left + width - padding, top + height - padding)  # 9: 右下顶点
                ]
                start_x, start_y = points[start_point - 1]
                end_x, end_y = points[end_point - 1]
                pyautogui.moveTo(start_x, start_y)
                pyautogui.mouseDown()
                pyautogui.moveTo(end_x, end_y, duration=duration)  # 使用 duration 参数控制滑行时间
                pyautogui.mouseUp()
                logging.info(f"成功在图片 {image_path} 上从点 {start_point} 拖动到点 {end_point}，考虑内距离 {padding}，用时 {duration} 秒")
                return True
            else:
                logging.info(f"第 {retries + 1} 次尝试未找到图片: {image_path}")
        except FileNotFoundError:
            logging.error(f"图片文件未找到: {image_path}")
            break
        except Exception as e:
            logging.error(f"触发冗余错误: {e}")
            break
        retries += 1
        time.sleep(retry_delay)
    logging.warning(f"达到最大重试次数，未找到图片: {image_path}")
    return False

def simulate_keyboard_input(text, interval=0.1):
    """
    模拟键盘输入指定的文本，支持中文输入。

    :param text: 要输入的文本
    :param interval: 每个字符输入之间的间隔时间（秒），默认值为 0.1
    """
    try:
        # 将文本复制到剪贴板
        pyperclip.copy(text)
        # 模拟粘贴操作
        pyautogui.hotkey('ctrl', 'v')
        logging.info(f"成功模拟键盘输入: {text}")
    except Exception as e:
        logging.error(f"模拟键盘输入时发生错误: {e}")


def read_json_file(file_path):
    """
    读取指定路径的 JSON 文件并返回其内容。

    :param file_path: JSON 文件的路径
    :return: JSON 文件的内容，如果文件不存在或读取失败则返回 None
    """
    # 获取调用者的文件路径
    caller_file = inspect.stack()[1].filename
    # 假设 file_path 是相对调用者文件的路径，这里可以将其转换为绝对路径
    full_file_path = get_full_image_path(caller_file, file_path)
    try:
        with open(full_file_path, 'r', encoding='utf-8') as file:
            return json.load(file)
    except FileNotFoundError:
        logging.error(f"错误：JSON 文件 {full_file_path} 未找到。")
    except json.JSONDecodeError:
        logging.error(f"错误：无法解析 JSON 文件 {full_file_path}。")
    except Exception as e:
        logging.error(f"触发冗余错误: {e}")
    return None

if __name__ == "__main__":
    # relative_image_path = "./img/1.png"
    # # 示例：点击图片上的第 6 个点，内距离为 3 像素
    # find_and_click_point(relative_image_path, 6, 3)
    # # 示例：点击图片中心点向右偏移 30 像素，向上偏移 20 像素的位置
    # find_and_click_offset(relative_image_path, 30, 20)
    # # 示例：鼠标在图片中心点按下，滑动到相对偏移位置
    # mouse_drag_from_center(relative_image_path, 30, 20, duration=1.0)
    # # 示例：鼠标在图片上的点 1 按下，滑动到点 3 抬起
    # # 示例：模拟键盘输入
    # simulate_keyboard_input("Hello, World!")

    # 示例：读取 JSON 文件
    json_file_path = "./example.json"
    json_data = read_json_file(json_file_path)
    if json_data:
        print(json_data)